#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ESP8266httpUpdate.h>
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h>


#define LED 2

const int MAX_JSON_SIZE = 3603;  // 假设 JSON 数据的最大长度为 200 字节
char jsonBuffer[MAX_JSON_SIZE];
DynamicJsonDocument jsonDoc(MAX_JSON_SIZE);


// MQTT Broker
const char *mqtt_broker = "broker.emqx.io";
const char *topic = "esp8266/driverid/10010/led";
const char *mqtt_username = "emqx";
const char *mqtt_password = "public";
const int mqtt_port = 1883;

const char *updateUrl = "http://abc.oss-cn-beijing.aliyuncs.com/abc.bin";

bool ledState = false;


#define LED_PIN 4    // 定义 LED 灯带的数据引脚
#define NUM_LEDS 60  // 定义 LED 灯带上 LED 的数量


Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);



WiFiClient espClient;
PubSubClient client(espClient);

// 智能配网
void smartConfig() {
  WiFi.mode(WIFI_STA);
  Serial.println("\r\nWait for Smartconfig...");
  WiFi.beginSmartConfig();
  while (1) {
    Serial.print(".");
    delay(500);              // wait for a second
    digitalWrite(LED, LOW);  //亮板载蓝灯
    delay(500);
    digitalWrite(LED, HIGH);  //灭板载蓝灯
    if (WiFi.smartConfigDone()) {
      Serial.println("SmartConfig Success");
      Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
      Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
      mqtt();
      break;
    }
  }
}
// 自动配网
bool autoConfig() {
  WiFi.begin();
  //20秒连接不上自动进入配置程序
  for (int i = 0; i < 20; i++) {
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED) {
      Serial.println("WIFI SmartConfig Success");
      Serial.printf("SSID:%s", WiFi.SSID().c_str());
      Serial.printf(", PSW:%s\r\n", WiFi.psk().c_str());
      Serial.print("LocalIP:");
      Serial.print(WiFi.localIP());
      Serial.print(" ,GateIP:");
      Serial.println(WiFi.gatewayIP());
      mqtt();
      return true;
    } else {
      Serial.print("WIFI AutoConfig Waiting......");
      Serial.println(i);
      delay(1000);
    }
  }
  Serial.println("WIFI AutoConfig Faild!");
  return false;
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin(); 
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin() {
  Serial.println("---------upload--------------");
  t_httpUpdate_return ret = ESPhttpUpdate.update(espClient, updateUrl);
  switch (ret) {
    case HTTP_UPDATE_FAILED:  //当升级失败
      Serial.println("[update] Update failed.");
      break;
    case HTTP_UPDATE_NO_UPDATES:  //当无升级
      Serial.println("[update] Update no Update.");
      break;
    case HTTP_UPDATE_OK:  //当升级成功
      Serial.println("[update] Update ok.");
      break;
  }
}

void mqtt() {
  client.disconnect();  // 关闭PubSubClient连接
  espClient.stop();     // 关闭WiFiClient连接
    // Connecting to an MQTT broker
  client.setServer(mqtt_broker, mqtt_port);
  client.setCallback(callback);
  while (!client.connected()) {
    String client_id = "esp8266-client-";
    client_id += String(WiFi.macAddress());
    Serial.printf("The client %s connects to the public MQTT broker\n", client_id.c_str());
    if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
      Serial.println("Public EMQX MQTT broker connected");
    } else {
      Serial.print("Failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }

  // Publish and subscribe
  client.publish(topic, "hello emqx");
  client.subscribe(topic);
}



void setup() {
  // Set software serial baud to 115200;
  Serial.begin(115200);
  delay(1000);  // Delay for stability

  // init LED
  strip.begin();  // 初始化 NeoPixel
  strip.show();   // 显示初始状态（全黑）


  if (!autoConfig()) {
    smartConfig();
  }
  pinMode(0, INPUT);      //定义gpio0脚为输入模式，也就是和flash键连接的脚
  digitalWrite(0, HIGH);  //定义gpio0脚为高电平，注意，不能长期低电平，低电平就进入刷机模式

  // Setting LED pin as output
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);  // Turn off the LED initially
  // 设置下载程序绑定led
  ESPhttpUpdate.setLedPin(LED, LOW);
}

void callback(char *topic, byte *payload, unsigned int length) {
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message: ");
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];  // Convert *byte to string
  }
  Serial.print(message);

  // 将 MQTT 消息复制到 jsonBuffer 中
  memcpy(jsonBuffer, payload, length);
  jsonBuffer[length] = '\0';  // 添加字符串结束符
                              // 解析 JSON 数据
  deserializeJson(jsonDoc, jsonBuffer);

  if (message == "on" && !ledState) {
    digitalWrite(LED, LOW);  // Turn on the LED
    ledState = true;
  }
  if (message == "off" && ledState) {
    digitalWrite(LED, HIGH);  // Turn off the LED
    ledState = false;
  }
  if (message == "upload") {
    updateBin();
  }
  if (message == "clear") {
    strip.clear();  // 将所有 LED 的颜色设置为黑色（关闭）
    strip.show();   // 更新 LED 状态
  }
  Serial.println();
  Serial.println("-----------------------");
}

// 将所有 LED 设置为指定的颜色
void colorWipe(uint32_t color, int i) {
  strip.setPixelColor(i, color);
  strip.show();
}

void loop() {
  client.loop();
  if (GPIO_INPUT_GET(0) == LOW)  //按下flash键，gpio0变为低电平，如果gpio0脚为低电平，则进入wifi重置模式
  {
    delay(500);
    client.disconnect();  // 关闭PubSubClient连接
    espClient.stop();     // 关闭WiFiClient连接
    Serial.print("Rest wifi...\n");
    delay(1000);
    Serial.print("wifi Resting...\n");
    smartConfig();
  }
  delay(100);  // Delay for a short period in each loop iteration

  // 解析 JSON 对象中的数据，并设置灯带颜色
  int numPixels = jsonDoc.size();
  if (numPixels > 0) {
    for (int i = 0; i < numPixels; i++) {
      JsonObject pixel = jsonDoc[i];
      int l = pixel["index"].as<int>();
      ;
      int r = pixel["r"];
      int g = pixel["g"];
      int b = pixel["b"];

      Serial.println();
      Serial.print("index:");
      Serial.print(l);
      Serial.print("colorStr:");
      Serial.println(r);
      Serial.println(g);
      Serial.println(b);
      colorWipe(strip.Color(r, g, b), l);
      Serial.println("-----------------------");
    }
    jsonDoc.clear();
  }
}
